package planning.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import commons.cloud.MachineType;

/**
 * This class aims to evaluate resource consumption during a planning interval and define an optimal 
 * reservation plan.
 * @author David Candeia
 *
 */
public class OptimumPlanning {
	
	private static final String LIGHT_PROVIDER = "Light";
	private static final String MEDIUM_PROVIDER = "Medium";
	private static final String HEAVY_PROVIDER = "Heavy";
	
	protected static HashMap<String, Map<MachineType, Double>> reservationFees;
	protected static HashMap<MachineType, Double> onDemandFees;
	protected static HashMap<String, Map<MachineType, Double>> reservedFees;
	protected static HashMap<String, Map<MachineType, Double>> minimumConsumption;
	protected static double monitoringFee;
	
	protected static double totalDemand;
	protected static double cost;
	
	private static long minimumConsumed;
	private static long maximumConsumed;
	
	
	public static void main(String[] args) {
		if(args.length != 2){
			System.err.println("Usage: <resource consumption file> <receipts file>");
			System.exit(1);
		}
		
		initValues();
		Map<MachineType, Map<Long, Double>> machinesUsagePerType = readMachineUsage(args[0]);
		double totalReceipt = readReceipt(args[1]);
		
		//Finding maximum number of machines that could be reserved
		Map<MachineType, Long> reservationLimit = findReservationLimit(totalDemand, minimumConsumption);
		
		//Searching for the best reservation plan
		Map<String, Map<MachineType, Long>> optimumReservationPlan = findOptimumReservationPlan(totalReceipt, reservationLimit, machinesUsagePerType);
		
		//Persisting data
		saveData(optimumReservationPlan, totalReceipt, cost, args[0]);
	}
	
	/**
	 * This method saves the optimum plan found as well as business information
	 * @param optimumReservationPlan
	 * @param totalReceipt
	 * @param cost
	 */
	protected static void saveData(Map<String, Map<MachineType, Long>> optimumReservationPlan,
			double totalReceipt, double cost, String usageFile) {
		String scenario = usageFile.split("_")[1];
		try {
			FileWriter writer = new FileWriter(new File("data_"+scenario));
			
			//Utility, receipt and cost
			writer.write((totalReceipt - cost)+"\t"+totalReceipt+"\t"+cost+"\n");
			
			//Reservation plan
			for(Entry<String, Map<MachineType, Long>> entry : optimumReservationPlan.entrySet()){
				
				writer.write(entry.getKey());
				
				for(Entry<MachineType, Long> entry2 : entry.getValue().entrySet()){
					writer.write("\t"+entry2.getKey().toString()+":\t"+entry2.getValue());
				}
			}
			writer.write("\n");
			writer.close();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * This method searches for an optimum reservation plan
	 * @param totalReceipt Total receipt to receive
	 * @param reservationLimit Maximum machines to reserve for each machine type
	 * @param machinesUsagePerType Machines consumption per machine type
	 * @return
	 */
	protected static Map<String, Map<MachineType, Long>> findOptimumReservationPlan(double totalReceipt,
			Map<MachineType, Long> reservationLimit, Map<MachineType, Map<Long, Double>> machinesUsagePerType) {
		
		double bestCost = Double.MAX_VALUE;
		Map<String, Map<MachineType, Long>> bestReservationPerProvider = new HashMap<String, Map<MachineType,Long>>();
		
		//Updating machines consumption considering cumulative consumption: if 10 machines were used for 1 hour, then 9 machines were also used for 1 hour, ...
		Map<Long, Double> accumulatedConsumption = updateConsumption(machinesUsagePerType);
		ArrayList<Long> numberOfMachinesList = new ArrayList<Long>(accumulatedConsumption.keySet());
		Collections.sort(numberOfMachinesList);
		
		//Building all financially viable reservation plans
		//TODO: Check costs!
		for(int smallInstancesAvailable = 0; smallInstancesAvailable < reservationLimit.get(MachineType.M1_SMALL); smallInstancesAvailable++){
			for(int largeInstancesAvailable = 0; largeInstancesAvailable < reservationLimit.get(MachineType.M1_LARGE); largeInstancesAvailable++){
				for(int xLargeInstancesAvailable = 0; xLargeInstancesAvailable < reservationLimit.get(MachineType.M1_XLARGE); xLargeInstancesAvailable++){
					
					double usageFeesPaid = 0d;
					double reservationFeesPaid = 0d;
					long smallAlreadyReserved = 0;
					long largeAlreadyReserved = 0;
					long xlargeAlreadyReserved = 0;
					long onDemandAlreadyUsed = 0;
					
					Map<String, Map<MachineType, Long>> currentReservationPerProvider = initPlanMap();
					
					//Checking consumption starting from the smallest amount of machines (mostly used) to the highest amount
					for(long machinesRequested : numberOfMachinesList){
						
						long totalMachines = machinesRequested - smallAlreadyReserved - largeAlreadyReserved - xlargeAlreadyReserved - onDemandAlreadyUsed;
						double totalHours = accumulatedConsumption.get(machinesRequested);
						
						if((xLargeInstancesAvailable - xlargeAlreadyReserved) > 0 && totalMachines > 0){//Consuming xLarge instances
							long machinesConsumed = Math.min(totalMachines, (xLargeInstancesAvailable - xlargeAlreadyReserved));

							if(totalHours > minimumConsumption.get(LIGHT_PROVIDER).get(MachineType.M1_XLARGE)){//Is there advantage of reserving a light instance?
								
								double hoursChangeTarget = (reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_XLARGE) - reservationFees.get(LIGHT_PROVIDER).get(MachineType.M1_XLARGE))
										/ (reservedFees.get(LIGHT_PROVIDER).get(MachineType.M1_XLARGE) - reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_XLARGE));
								if(totalHours > hoursChangeTarget){//Is there and advantage of changing from light to medium reservation market?
									
									hoursChangeTarget = (reservationFees.get(HEAVY_PROVIDER).get(MachineType.M1_XLARGE) - reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_XLARGE))
											/ (reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_XLARGE) - reservedFees.get(HEAVY_PROVIDER).get(MachineType.M1_XLARGE));
									
									if(totalHours > hoursChangeTarget){//Is there and advantage of changing from medium to high reservation market?
										usageFeesPaid += (machinesConsumed * 8760) * (monitoringFee);
										reservationFeesPaid += machinesConsumed * reservationFees.get(HEAVY_PROVIDER).get(MachineType.M1_XLARGE);
										
										//Updating current reservations
										Long currentReserved = currentReservationPerProvider.get(HEAVY_PROVIDER).get(MachineType.M1_XLARGE);
										currentReservationPerProvider.get(HEAVY_PROVIDER).put(MachineType.M1_XLARGE, currentReserved+machinesConsumed);
									}else{//Remains in medium market
										usageFeesPaid += (machinesConsumed * totalHours)* (reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_XLARGE) + monitoringFee);
										reservationFeesPaid += machinesConsumed * reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_XLARGE);
										
										//Updating current reservations
										Long currentReserved = currentReservationPerProvider.get(MEDIUM_PROVIDER).get(MachineType.M1_XLARGE);
										currentReservationPerProvider.get(MEDIUM_PROVIDER).put(MachineType.M1_XLARGE, currentReserved+machinesConsumed);
									}
									
								}else{//Remains in light market
									usageFeesPaid += (machinesConsumed * totalHours)* (reservedFees.get(LIGHT_PROVIDER).get(MachineType.M1_XLARGE) + monitoringFee);
									reservationFeesPaid += machinesConsumed * reservationFees.get(LIGHT_PROVIDER).get(MachineType.M1_XLARGE);
									
									//Updating current reservations
									Long currentReserved = currentReservationPerProvider.get(LIGHT_PROVIDER).get(MachineType.M1_XLARGE);
									currentReservationPerProvider.get(LIGHT_PROVIDER).put(MachineType.M1_XLARGE, currentReserved+machinesConsumed);
								}
							}
//							long toConsume = Math.min(totalMachines, (xLargeInstancesAvailable - xlargeAlreadyReserved) * 4);
							long toConsume = Math.min(totalMachines, (xLargeInstancesAvailable - xlargeAlreadyReserved) * 8);
							totalMachines -= toConsume;
							xlargeAlreadyReserved += machinesConsumed;
						}
						
						if((largeInstancesAvailable - largeAlreadyReserved) > 0 && totalMachines > 0){//Consuming large instances
							long machinesConsumed = Math.min(totalMachines, (largeInstancesAvailable - largeAlreadyReserved));
							
							if(totalHours > minimumConsumption.get(LIGHT_PROVIDER).get(MachineType.M1_LARGE)){//Is there advantage of reserving a light instance?
								
								double hoursChangeTarget = (reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_LARGE) - reservationFees.get(LIGHT_PROVIDER).get(MachineType.M1_LARGE))
										/ (reservedFees.get(LIGHT_PROVIDER).get(MachineType.M1_LARGE) - reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_LARGE));
								if(totalHours > hoursChangeTarget){//Is there and advantage of changing from light to medium reservation market?
									
									hoursChangeTarget = (reservationFees.get(HEAVY_PROVIDER).get(MachineType.M1_LARGE) - reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_LARGE))
											/ (reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_LARGE) - reservedFees.get(HEAVY_PROVIDER).get(MachineType.M1_LARGE));
									
									if(totalHours > hoursChangeTarget){//Is there and advantage of changing from medium to high reservation market?
										usageFeesPaid += (machinesConsumed * 8760) * (monitoringFee);
										reservationFeesPaid += machinesConsumed * reservationFees.get(HEAVY_PROVIDER).get(MachineType.M1_LARGE);
										
										//Updating current reservations
										Long currentReserved = currentReservationPerProvider.get(HEAVY_PROVIDER).get(MachineType.M1_LARGE);
										currentReservationPerProvider.get(HEAVY_PROVIDER).put(MachineType.M1_LARGE, currentReserved+machinesConsumed);
									}else{//Remains in medium market
										usageFeesPaid += (machinesConsumed * totalHours)* (reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_LARGE) + monitoringFee);
										reservationFeesPaid += machinesConsumed * reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_LARGE);
										
										//Updating current reservations
										Long currentReserved = currentReservationPerProvider.get(MEDIUM_PROVIDER).get(MachineType.M1_LARGE);
										currentReservationPerProvider.get(MEDIUM_PROVIDER).put(MachineType.M1_LARGE, currentReserved+machinesConsumed);
									}
									
								}else{//Remains in light market
									usageFeesPaid += (machinesConsumed * totalHours)* (reservedFees.get(LIGHT_PROVIDER).get(MachineType.M1_LARGE) + monitoringFee);
									reservationFeesPaid += machinesConsumed * reservationFees.get(LIGHT_PROVIDER).get(MachineType.M1_LARGE);
									
									//Updating current reservations
									Long currentReserved = currentReservationPerProvider.get(LIGHT_PROVIDER).get(MachineType.M1_LARGE);
									currentReservationPerProvider.get(LIGHT_PROVIDER).put(MachineType.M1_LARGE, currentReserved+machinesConsumed);
								}
							}
//							long toConsume = Math.min(totalMachines, (largeInstancesAvailable - largeAlreadyReserved) * 2);
							long toConsume = Math.min(totalMachines, (largeInstancesAvailable - largeAlreadyReserved) * 4);
							totalMachines -= toConsume;
							largeAlreadyReserved += machinesConsumed;
						}
						
						if((smallInstancesAvailable - smallAlreadyReserved) > 0 && totalMachines > 0){//Consuming small instances
							long toConsume = Math.min(totalMachines, (smallInstancesAvailable - smallAlreadyReserved));
							
							if(totalHours > minimumConsumption.get(LIGHT_PROVIDER).get(MachineType.M1_SMALL)){//Is there advantage of reserving a light instance?
								
								double hoursChangeTarget = (reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_SMALL) - reservationFees.get(LIGHT_PROVIDER).get(MachineType.M1_SMALL))
										/ (reservedFees.get(LIGHT_PROVIDER).get(MachineType.M1_SMALL) - reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_SMALL));
								if(totalHours > hoursChangeTarget){//Is there and advantage of changing from light to medium reservation market?
									
									hoursChangeTarget = (reservationFees.get(HEAVY_PROVIDER).get(MachineType.M1_SMALL) - reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_SMALL))
											/ (reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_SMALL) - reservedFees.get(HEAVY_PROVIDER).get(MachineType.M1_SMALL));
									if(totalHours > hoursChangeTarget){//Is there and advantage of changing from medium to high reservation market?
										usageFeesPaid += (toConsume * 8760) * (monitoringFee);
										reservationFeesPaid += toConsume * reservationFees.get(HEAVY_PROVIDER).get(MachineType.M1_SMALL);
										
										//Updating current reservations
										Long currentReserved = currentReservationPerProvider.get(HEAVY_PROVIDER).get(MachineType.M1_SMALL);
										currentReservationPerProvider.get(HEAVY_PROVIDER).put(MachineType.M1_SMALL, currentReserved+toConsume);
									}else{//Remains in medium market
										usageFeesPaid += (toConsume * totalHours)* (reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_SMALL) + monitoringFee);
										reservationFeesPaid += toConsume * reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_SMALL);
										
										//Updating current reservations
										Long currentReserved = currentReservationPerProvider.get(MEDIUM_PROVIDER).get(MachineType.M1_SMALL);
										currentReservationPerProvider.get(MEDIUM_PROVIDER).put(MachineType.M1_SMALL, currentReserved+toConsume);
									}
									
								}else{//Remains in light market
									usageFeesPaid += (toConsume * totalHours)* (reservedFees.get(LIGHT_PROVIDER).get(MachineType.M1_SMALL) + monitoringFee);
									reservationFeesPaid += toConsume * reservationFees.get(LIGHT_PROVIDER).get(MachineType.M1_SMALL);
									
									//Updating current reservations
									Long currentReserved = currentReservationPerProvider.get(LIGHT_PROVIDER).get(MachineType.M1_SMALL);
									currentReservationPerProvider.get(LIGHT_PROVIDER).put(MachineType.M1_SMALL, currentReserved+toConsume);
								}
							}
							
							totalMachines -= toConsume;
							smallAlreadyReserved += toConsume;
						}
						
						//On-demand instances
						if(totalMachines > 0){
							usageFeesPaid += (totalMachines * totalHours)* (onDemandFees.get(MachineType.M1_SMALL) + monitoringFee);
							onDemandAlreadyUsed += totalMachines;
						}
					}
					
					//Checking if a better cost was found!
					if(usageFeesPaid + reservationFeesPaid < bestCost){
						bestCost = usageFeesPaid + reservationFeesPaid;
						bestReservationPerProvider = currentReservationPerProvider;
					}
				}
				
			}
			
		}
		
		cost = bestCost; 
		
		return bestReservationPerProvider;
	}
	
	/**
	 * This method inits a map without reservations in all available providers
	 * @return
	 */
	private static Map<String, Map<MachineType, Long>> initPlanMap() {
		Map<String, Map<MachineType, Long>> currentReservationPerProvider = new HashMap<String, Map<MachineType,Long>>();
		
		HashMap<MachineType, Long> lightReservations = new HashMap<MachineType, Long>();
		lightReservations.put(MachineType.M1_SMALL, 0l);
		lightReservations.put(MachineType.M1_LARGE, 0l);
		lightReservations.put(MachineType.M1_XLARGE, 0l);
		currentReservationPerProvider.put(LIGHT_PROVIDER, lightReservations);
		
		HashMap<MachineType, Long> mediumReservations = new HashMap<MachineType, Long>();
		mediumReservations.put(MachineType.M1_SMALL, 0l);
		mediumReservations.put(MachineType.M1_LARGE, 0l);
		mediumReservations.put(MachineType.M1_XLARGE, 0l);
		currentReservationPerProvider.put(MEDIUM_PROVIDER, mediumReservations);
		
		HashMap<MachineType, Long> highReservations = new HashMap<MachineType, Long>();
		highReservations.put(MachineType.M1_SMALL, 0l);
		highReservations.put(MachineType.M1_LARGE, 0l);
		highReservations.put(MachineType.M1_XLARGE, 0l);
		currentReservationPerProvider.put(HEAVY_PROVIDER, highReservations);
		
		return currentReservationPerProvider;
	}

	/**
	 * This method evaluates application demand in order to estimate the maximum 
	 * number of machines that could be reserved
	 * @param totalDemand
	 * @param minimumConsumption2
	 * @return
	 */
	protected static Map<MachineType, Long> findReservationLimit(double totalDemand,
			Map<String, Map<MachineType, Double>> minimumConsumption2) {
		
		Map<MachineType, Long> reservationLimit = new HashMap<MachineType, Long>();
		
		//Assuming that all providers offer the same machine types
		Map<MachineType, Double> minimumLightConsumption = minimumConsumption2.get(LIGHT_PROVIDER);
		for(Entry<MachineType, Double> entry : minimumLightConsumption.entrySet()){
//			long limit = (long) Math.ceil(totalDemand / minimumConsumption.get(type));
//			reservationLimit.put(type, limit);
			reservationLimit.put(entry.getKey(), maximumConsumed);
		}
		
		return reservationLimit;
	}

	/**
	 * Machine types and prices considered in simulation
	 */
	protected static void initValues() {
		reservationFees = new HashMap<String, Map<MachineType,Double>>();
		
		Map<MachineType, Double> lightProvider = new HashMap<MachineType, Double>();
		lightProvider.put(MachineType.M1_SMALL, 61.0);
		lightProvider.put(MachineType.M1_LARGE, 243.0);
		lightProvider.put(MachineType.M1_XLARGE, 486.0);
		reservationFees.put(LIGHT_PROVIDER, lightProvider);
		
		Map<MachineType, Double> mediumProvider = new HashMap<MachineType, Double>();
		mediumProvider.put(MachineType.M1_SMALL, 139.0);
		mediumProvider.put(MachineType.M1_LARGE, 554.0);
		mediumProvider.put(MachineType.M1_XLARGE, 1108.0);
		reservationFees.put(MEDIUM_PROVIDER, mediumProvider);
		
		//High utilization instances are charged for a whole year usage
		Map<MachineType, Double> highProvider = new HashMap<MachineType, Double>();
		highProvider.put(MachineType.M1_SMALL, 169 + 8760 * 0.014);
		highProvider.put(MachineType.M1_LARGE, 676 + 8760 * 0.056);
		highProvider.put(MachineType.M1_XLARGE, 1352 + 8760 * 0.112);
		reservationFees.put(HEAVY_PROVIDER, highProvider);
		
		//On-demand fees are the same for all providers
		onDemandFees = new HashMap<MachineType, Double>();
		onDemandFees.put(MachineType.M1_SMALL, 0.06);
		onDemandFees.put(MachineType.M1_LARGE, 0.24);
		onDemandFees.put(MachineType.M1_XLARGE, 0.48);
		
		//Reserved instances usage fees
		reservedFees = new HashMap<String, Map<MachineType,Double>>();

		Map<MachineType, Double> lightProviderUsage = new HashMap<MachineType, Double>();
		lightProviderUsage.put(MachineType.M1_SMALL, 0.034);
		lightProviderUsage.put(MachineType.M1_LARGE, 0.136);
		lightProviderUsage.put(MachineType.M1_XLARGE, 0.271);
		reservedFees.put(LIGHT_PROVIDER, lightProviderUsage);
		
		Map<MachineType, Double> mediumProviderUsage = new HashMap<MachineType, Double>();
		mediumProviderUsage.put(MachineType.M1_SMALL, 0.021);
		mediumProviderUsage.put(MachineType.M1_LARGE, 0.084);
		mediumProviderUsage.put(MachineType.M1_XLARGE, 0.168);
		reservedFees.put(MEDIUM_PROVIDER, mediumProviderUsage);
		
		//High utilization market resources do not have usage fee since they're charged for the whole year usage
		Map<MachineType, Double> highProviderUsage = new HashMap<MachineType, Double>();
		highProviderUsage.put(MachineType.M1_SMALL, 0.0);
		highProviderUsage.put(MachineType.M1_LARGE, 0.0);
		highProviderUsage.put(MachineType.M1_XLARGE, 0.0);
		reservedFees.put(HEAVY_PROVIDER, highProviderUsage);
		
		monitoringFee = 0.005;

		minimumConsumption = new HashMap<String, Map<MachineType,Double>>();
		
		//Minimum Consumption for Light provider
		Map<MachineType, Double> lightMinimumConsumption =  new HashMap<MachineType, Double>();
		double smallConsumption = reservationFees.get(LIGHT_PROVIDER).get(MachineType.M1_SMALL) / (onDemandFees.get(MachineType.M1_SMALL) - reservedFees.get(LIGHT_PROVIDER).get(MachineType.M1_SMALL));
		lightMinimumConsumption.put(MachineType.M1_SMALL, smallConsumption);
		
		double largeConsumption = reservationFees.get(LIGHT_PROVIDER).get(MachineType.M1_LARGE) / (onDemandFees.get(MachineType.M1_LARGE) - reservedFees.get(LIGHT_PROVIDER).get(MachineType.M1_LARGE));
		lightMinimumConsumption.put(MachineType.M1_LARGE, largeConsumption);
		
		double xLargeConsumption = reservationFees.get(LIGHT_PROVIDER).get(MachineType.M1_XLARGE) / (onDemandFees.get(MachineType.M1_XLARGE) - reservedFees.get(LIGHT_PROVIDER).get(MachineType.M1_XLARGE));
		lightMinimumConsumption.put(MachineType.M1_XLARGE, xLargeConsumption);
		minimumConsumption.put(LIGHT_PROVIDER, lightMinimumConsumption);
		
		//Minimum Consumption for Medium provider
		Map<MachineType, Double> mediumMinimumConsumption =  new HashMap<MachineType, Double>();
		smallConsumption = reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_SMALL) / (onDemandFees.get(MachineType.M1_SMALL) - reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_SMALL));
		mediumMinimumConsumption.put(MachineType.M1_SMALL, smallConsumption);
		
		largeConsumption = reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_LARGE) / (onDemandFees.get(MachineType.M1_LARGE) - reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_LARGE));
		mediumMinimumConsumption.put(MachineType.M1_LARGE, largeConsumption);
		
		xLargeConsumption = reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_XLARGE) / (onDemandFees.get(MachineType.M1_XLARGE) - reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_XLARGE));
		mediumMinimumConsumption.put(MachineType.M1_XLARGE, xLargeConsumption);
		minimumConsumption.put(MEDIUM_PROVIDER, mediumMinimumConsumption);
				
		//Minimum Consumption for High provider
		Map<MachineType, Double> highMinimumConsumption =  new HashMap<MachineType, Double>();
		smallConsumption = reservationFees.get(HEAVY_PROVIDER).get(MachineType.M1_SMALL) / (onDemandFees.get(MachineType.M1_SMALL) - reservedFees.get(HEAVY_PROVIDER).get(MachineType.M1_SMALL));
		highMinimumConsumption.put(MachineType.M1_SMALL, smallConsumption);
		
		largeConsumption = reservationFees.get(HEAVY_PROVIDER).get(MachineType.M1_LARGE) / (onDemandFees.get(MachineType.M1_LARGE) - reservedFees.get(HEAVY_PROVIDER).get(MachineType.M1_LARGE));
		highMinimumConsumption.put(MachineType.M1_LARGE, largeConsumption);
		
		xLargeConsumption = reservationFees.get(HEAVY_PROVIDER).get(MachineType.M1_XLARGE) / (onDemandFees.get(MachineType.M1_XLARGE) - reservedFees.get(HEAVY_PROVIDER).get(MachineType.M1_XLARGE));
		highMinimumConsumption.put(MachineType.M1_XLARGE, xLargeConsumption);
		minimumConsumption.put(HEAVY_PROVIDER, highMinimumConsumption);
		
		totalDemand = 0;
		cost = 0;
		minimumConsumed = Long.MAX_VALUE;
		maximumConsumed = Long.MIN_VALUE;
	}

	/**
	 * This method returns a map containing for each number of machines used the amount of hours consumed
	 * @param file
	 * @return
	 */
//	protected static Map<Long, Double> readMachineUsage(String file) {
//		//Input format: ACC:    0       5       5
//		
//		//Reading machine consumption from file
//		Map<Long, Double> usageData = new HashMap<Long, Double>();
//		
//		try {
//			BufferedReader reader = new BufferedReader(new FileReader(new File(file)));
//			while(reader.ready()){
//				String[] split = reader.readLine().split("\\s+");
//				long numberOfMachinesConsumed = Long.valueOf(split[3]);
//				
//				Double hoursConsumed = usageData.get(numberOfMachinesConsumed);
//				if(hoursConsumed == null){
//					hoursConsumed = 0d;
//				}
//				
//				hoursConsumed++;
//				usageData.put(numberOfMachinesConsumed, hoursConsumed);
//				
//				if(numberOfMachinesConsumed < minimumConsumed){
//					minimumConsumed = numberOfMachinesConsumed;
//				}else if(numberOfMachinesConsumed > maximumConsumed){
//					maximumConsumed = numberOfMachinesConsumed;
//				}
//			}
//			reader.close();
//		} catch (FileNotFoundException e) {
//			throw new RuntimeException(e);
//		} catch (IOException e) {
//			throw new RuntimeException(e);
//		}
//		
//		//Calculating total demand
//		for(Entry<Long, Double> entry : usageData.entrySet()){
//			totalDemand += entry.getKey() * entry.getValue(); 
//		}
//		
////		//Updating hours used
////		Map<Long, Double> newUsageData = new HashMap<Long, Double>();
////		
////		ArrayList<Long> numberOfMachinesList = new ArrayList<Long>(usageData.keySet());
////		Collections.sort(numberOfMachinesList);
////		Collections.reverse(numberOfMachinesList);
////		
////		newUsageData.put(numberOfMachinesList.get(0), usageData.get(numberOfMachinesList.get(0)));//First value
////		
////		for(Long numberOfMachinesUsed : numberOfMachinesList){//Iterating from the highest number of machines to the lowest one
////			double higherAmountUsed = usageData.get(numberOfMachinesUsed);
////			
////			for(long i = numberOfMachinesUsed - 1; i > 0; i--){//If 15 machines were used for 10 hours, 10 machines were also used for 10 hours ...
////				Double lowerAmountUsed = (newUsageData.get(i) != null) ? newUsageData.get(i) : usageData.get(i);
////				
////				if(lowerAmountUsed != null){
////					newUsageData.put(i, lowerAmountUsed + higherAmountUsed);
////				}
////			}
////		}
//		
//		return usageData;
//	}
	/**
	 * This method retrieves machine consumption per machine type during the simulation
	 * @param file
	 * @return
	 */
	protected static Map<MachineType, Map<Long, Double>> readMachineUsage(String file) {
		//Input format: Provider: + provider.getName();Reserved:reservedResources;OnDemand:onDemandResources
		
		//Reading machine consumption from file
		Map<MachineType, Map<Long, Double>> usagePerType = new HashMap<MachineType, Map<Long,Double>>();
		
		try {
			BufferedReader reader = new BufferedReader(new FileReader(new File(file)));
			while(reader.ready()){
				String[] split = reader.readLine().split(";");//Splitting by ;
				
				String provider = split[0];
				String reservedResources = split[1].split(":")[1];
				String onDemandResources = split[2].split(":")[1];
				
				String reservedMap = reservedResources.substring(1, reservedResources.length()-1);
				String onDemandMap = onDemandResources.substring(1, onDemandResources.length()-1);
				
				Map<MachineType, Long> currentReservationLine = new HashMap<MachineType, Long>();
				Map<MachineType, Long> currentOnDemandLine = new HashMap<MachineType, Long>();
				
				//Reading reserved resources usage map
				if(!reservedMap.isEmpty()){
					String[] reservations = reservedMap.split(",");
					for(int  i = 0; i < reservations.length; i++){
						String[] data = reservations[i].split("=");
						MachineType currentType = MachineType.valueOf(data[0].trim());
						
						currentReservationLine.put(currentType, Long.valueOf(data[1].trim()));
					}
				}
				
				//Reading on-demand resources usage map
				if(!onDemandMap.isEmpty()){
					String[] consumption = onDemandMap.split(",");
					for(int  i = 0; i < consumption.length; i++){
						String[] data = consumption[i].split("=");
						MachineType currentType = MachineType.valueOf(data[0].trim());
						
						currentOnDemandLine.put(currentType, Long.valueOf(data[1].trim()));
					}
				}
				
				//Adding amounts of machines from same type used in different markets
				MachineType[] machineTypes = MachineType.values();
				for(MachineType machineType : machineTypes){
					Long reservedAmount = currentReservationLine.get(machineType);
					reservedAmount = (reservedAmount != null)?reservedAmount:0l;
					Long onDemandAmount = currentOnDemandLine.get(machineType);
					onDemandAmount = (onDemandAmount != null)?onDemandAmount:0l;
					
					Long amountOfMachinesUsed = reservedAmount + onDemandAmount;
					if(amountOfMachinesUsed > 0){
						Map<Long, Double> typeMap = usagePerType.get(machineType);//Consumption map for a certain machine type
						if(typeMap == null){
							typeMap = new HashMap<Long, Double>();
						}
						Double usage = typeMap.get(amountOfMachinesUsed);
						if(usage == null){
							usage = new Double(0);
						}
						typeMap.put(amountOfMachinesUsed, usage+1);
						usagePerType.put(machineType, typeMap);
					}
					if(amountOfMachinesUsed < minimumConsumed){
						minimumConsumed = amountOfMachinesUsed;
					}else if(amountOfMachinesUsed > maximumConsumed){
						maximumConsumed = amountOfMachinesUsed;
					}
				}
			}
			reader.close();
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		
		//Calculating total demand
		for(Entry<MachineType, Map<Long, Double>> entry : usagePerType.entrySet()){
			Map<Long, Double> value = entry.getValue();
			for(Entry<Long, Double> entry2 : value.entrySet()){
				totalDemand += entry2.getKey() * entry.getKey().getTotalCapacity() * entry2.getValue();
			}
		}
		
		return usagePerType;
	}
	
	private static Map<Long, Double> updateConsumption(Map<MachineType, Map<Long, Double>> hoursUsedPerType) {
		
		Map<Long, Double> hoursUsed = new HashMap<Long, Double>();
		
		//Creating a map of amount of cores used
		for(MachineType machineType : hoursUsedPerType.keySet()){
			Map<Long, Double> usageData = hoursUsedPerType.get(machineType);
			for(Entry<Long, Double> entry : usageData.entrySet()){
				if(machineType.equals(MachineType.M1_SMALL)){
					Double amountOfHours = hoursUsed.get(entry.getKey());
					if(amountOfHours == null){
						amountOfHours = new Double(0);
					}
					hoursUsed.put(entry.getKey(), amountOfHours + entry.getValue());
				}else if(machineType.equals(MachineType.M1_LARGE)){
					Double amountOfHours = hoursUsed.get(entry.getKey() * 2);
					if(amountOfHours == null){
						amountOfHours = new Double(0);
					}
					hoursUsed.put(entry.getKey() * 2, amountOfHours + entry.getValue());
				}else if(machineType.equals(MachineType.M1_XLARGE)){
					Double amountOfHours = hoursUsed.get(entry.getKey() * 4);
					if(amountOfHours == null){
						amountOfHours = new Double(0);
					}
					hoursUsed.put(entry.getKey() * 4, amountOfHours + entry.getValue());
				}
			}
		}
			
		//Updating cumulative consumption
		ArrayList<Long> numberOfMachinesList = new ArrayList<Long>(hoursUsed.keySet());
		Collections.sort(numberOfMachinesList);
		Collections.reverse(numberOfMachinesList);
		
		Map<Long, Double> newUsageData = new HashMap<Long, Double>();
		newUsageData.put(numberOfMachinesList.get(0), hoursUsed.get(numberOfMachinesList.get(0)));//First value
		
		for(Long numberOfMachinesUsed : numberOfMachinesList){//Iterating from the highest number of machines to the lowest one
			double higherUsed = hoursUsed.get(numberOfMachinesUsed);
			
			for(long i = numberOfMachinesUsed - 1; i > 0; i--){//If 15 machines were used for 10 hours, 10 machines were also used for 10 hours ...
				Double lowerUsed = (newUsageData.get(i) != null) ? newUsageData.get(i) : hoursUsed.get(i);
				
				if(lowerUsed != null){
					newUsageData.put(i, lowerUsed+higherUsed);
				}
			}
		}
		
		return newUsageData;
	}


	/**
	 * Reading receipt for each month from a previous generated file
	 * @param file
	 * @return
	 */
	protected static double readReceipt(String file) {
		double totalReceipt = 0d;
		
		try {
			BufferedReader reader = new BufferedReader(new FileReader(new File(file)));
			while(reader.ready()){
				String[] data = reader.readLine().split("\\s+");
				double currentReceipt = Double.valueOf(data[0]);
				double transferenceCosts = Double.valueOf(data[1]) + Double.valueOf(data[2]);
				totalReceipt += (currentReceipt - transferenceCosts);
			}
			reader.close();
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		} catch (NumberFormatException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		return totalReceipt;
	}

}
